`include "../include/common.sv"

module TageTable#(
	parameter INDEX_SIZE=7,
	parameter TAG_SIZE=9,
	parameter COUNTER_SIZE=3,
	parameter USE_SIZE=2,
	parameter READ_LATENCY=1
)(
	input logic cpu_clk,
	input logic cpu_rst_n,
	input logic real_branch,
	input logic update_en,
	input logic provider,
	input logic alloc,
	input logic update_use,
	input logic dec_use,
	input logic [INDEX_SIZE-1: 0] update_index,
	input logic [INDEX_SIZE-1: 0] search_index,
	input logic [TAG_SIZE-1: 0] search_tag,
	input logic [TAG_SIZE-1: 0] update_tag,
	output logic new_entry,
	output logic prediction,
	output logic tag_hit,
	output logic  out_u
);
	typedef struct packed {
		logic [TAG_SIZE-1: 0] tag;
		logic [COUNTER_SIZE-1: 0] ctr;
		logic [USE_SIZE-1: 0] u;
	} tag_element_t;
	tag_element_t update_ele_in, update_ele_out, search_ele;
	logic [INDEX_SIZE-1: 0] update_index_next;
	BSDPRAM #(
		.WORD_WIDTH(TAG_SIZE+COUNTER_SIZE+USE_SIZE),
		.DEPTH(`LSHIFTX(INDEX_SIZE)),
		.ADDR_WIDTH(INDEX_SIZE),
		.MEMORY_PRIMITIVE("block"),
		.READ_LATENCY(READ_LATENCY)
	) search_ram(
		.clk(cpu_clk),
		.ena(update_en),
		.addra(update_index_next),
		.wea(update_en),
		.dina(update_ele_in),
		.enb(1'b1),
		.addrb(search_index),
		.doutb(search_ele)
	);

	BSDPRAM #(
		.WORD_WIDTH(TAG_SIZE+COUNTER_SIZE+USE_SIZE),
		.DEPTH(`LSHIFTX(INDEX_SIZE)),
		.ADDR_WIDTH(INDEX_SIZE),
		.MEMORY_PRIMITIVE("block"),
		.READ_LATENCY(READ_LATENCY)
	) update_ram(
		.clk(cpu_clk),
		.ena(update_en),
		.addra(update_index_next),
		.wea(update_en),
		.dina(update_ele_in),
		.enb(1'b1),
		.addrb(update_index),
		.doutb(update_ele_out)
	);
	always_ff @(posedge cpu_clk)begin
		update_index_next <= update_index;
	end

	assign prediction = search_ele.ctr[COUNTER_SIZE-1];
	assign tag_hit = search_ele.tag == search_tag;
	assign out_u = update_ele_out.u != 2'b0;
	assign new_entry = ((search_ele.ctr == 3'b100 || search_ele.ctr == 3'b011) && search_ele.u == 2'b0);

	assign update_ele_in.tag = alloc ? update_tag : update_ele_out.tag;
	assign update_ele_in.u = alloc ? 0 : provider && update_use && real_branch == update_ele_out.ctr[2] && update_ele_out.u != 2'b11 ? update_ele_out.u + 1 : (dec_use || provider && update_use && !update_ele_out.ctr[1] && !update_ele_out.ctr[0]) && update_ele_out.u != 2'b00 ? update_ele_out.u - 1 : update_ele_out.u;
	always_comb begin
		if(alloc)begin
			update_ele_in.ctr = real_branch ? 3'b100 : 3'b011;
		end
		else if(provider)begin
			if(real_branch && update_ele_out.ctr != 3'b111)begin
				update_ele_in.ctr = update_ele_out.ctr + 1;
			end
			else if(!real_branch && update_ele_out.ctr != 3'b000)begin
				update_ele_in.ctr = update_ele_out.ctr - 1;
			end
			else begin
				update_ele_in.ctr = update_ele_out.ctr;
			end
		end
		else begin
			update_ele_in.ctr = update_ele_out.ctr;
		end
	end

endmodule